from dataclasses import dataclass
import os
from pathlib import Path
from typing import IO


@dataclass
class CFuncDec:
    sName: str = ''  # 原始函数名
    sMethName: str = ''  # 方法名
    sArgs: str = ''
    sRetType: str = ''
    tPos: tuple = (-1, -1)  # 起始/结束位置。结束位置以分号为准。


class CDllFunc:
    sName: str = ''  # 原始函数名
    sMethName: str = ''  # 方法名
    sMemberName: str = ''  # 成员名
    sFuncType: str = ''  # 函数类型别名

    lArgs: list = None  # 参数列表#tuple
    sArgs: str = ''  # 字符串形式的参数
    sRetType: str = ''

    def __init__(self, data: CFuncDec, sPrefix=''):
        self.sName = data.sName
        # self.sArgs=data.sArgs
        self.sRetType = data.sRetType

        self.sMethName = self.FuncNameToMethName(data.sName)
        self.sMemberName = self.FuncNameToMemberName(data.sName)
        self.sFuncType = self.FuncNameToTpye(data.sName, sPrefix)

        r = self.ParseArgs(data.sArgs)
        if type(r) is not list:
            print(r)
            raise Exception(r)

        self.lArgs = r
        self.sArgs = self.ArgListToStr(self.lArgs)
        return

    def ArgListToStr(self, lData: list) -> str:
        ret = '('
        for it in lData:
            try:
                ret += f'{it[0]} {it[1]}, '
            except BaseException as e:
                return e

        if len(lData) > 0:
            ret = ret[0:len(ret) - 2]
        ret += ')'

        return ret

    def ParseArgs(self, sData: str) -> list:
        ret = []

        sDataSt: str = sData.strip('( )')
        if len(sDataSt) <= 0:
            return ret

        lSpStr: list = sDataSt.split(',')

        for it in lSpStr:
            sArg: str = it.strip()
            r1 = sArg.rfind('*')
            r2 = sArg.rfind(' ')
            if r1 < 0 and r2 < 0:
                return Exception('cannot find arg name')
            if r1 < 0:
                r1 = r2
            iNameStart = r1 + 1

            sArgType = sArg[0:iNameStart].strip()
            sArgName = sArg[iNameStart:].strip()
            ret.append((sArgType, sArgName))

        return ret

    def FuncNameToMethName(self, sName) -> str:
        return sName

    def FuncNameToMemberName(self, sName) -> str:
        return 'm_pfun' + self.FuncNameToMethName(sName)

    def FuncNameToTpye(self, sName, sPrefix='') -> str:
        return 'FUNC_TYPE_' + sPrefix + '_' + self.FuncNameToMethName(sName)


class CDllFile:
    sHead: str = ''
    oOutputFileH: IO = None
    oOutputFileCPP: IO = None

    lFunc: list = None
    sClassName = ''  # 类名

    sAPITag: str = 'API'  # 输入头文件里的函数声明标志
    sSTDCall: str = '__stdcall'  # 输出头文件里的stdcall标志

    def Open(self, sHeadFile: str, sDllFile: str = None) -> int:
        try:
            with open(sHeadFile, 'r') as oFile:
                self.sHead = oFile.read()
                return len(self.sHead)
        except BaseException as e:
            return e

        return None

    def CreateFile(self, oFilePath: Path) -> IO:  # 创建输出文件
        ret = None
        try:
            ret = open(oFilePath, 'w', encoding='utf-8')

            ret.write(f'//{oFilePath.name}\n')
        except BaseException as e:
            return e
        return ret

    def WriteFuncType(self) -> int:
        sName: str = 'STDCALL'
        self.oOutputFileH.write(f'#define {sName} {self.sSTDCall}\n')

        for it in self.lFunc:
            oFunc: CDllFunc = it
            self.oOutputFileH.write(f'typedef {oFunc.sRetType} ({sName}*{oFunc.sFuncType}){oFunc.sArgs};\n')
        return 0

    def WriteLoadFunc(self, sOpt='public') -> int:

        if sOpt == 'public':
            self.oOutputFileH.write(f'int Load(char *pszFile);\n')
            self.oOutputFileH.write(f'int UNLoad();\n')
            # Load#[
            self.oOutputFileCPP.write(f'int {self.sClassName}::Load(char *pszFile)\n')
            self.oOutputFileCPP.write('{\nint r;\n')
            self.oOutputFileCPP.write('r=LoadDll(pszFile);\n')
            self.oOutputFileCPP.write('if(r<0) return r;\n')

            for it in self.lFunc:
                oFunc: CDllFunc = it
                self.oOutputFileCPP.write(f'{oFunc.sMemberName}=({oFunc.sFuncType})GetFunc(\"{oFunc.sName}\");\n')

            self.oOutputFileCPP.write('return 0;\n}\n')
            # ]

            self.oOutputFileCPP.write(f'int {self.sClassName}::UNLoad()\n')
            self.oOutputFileCPP.write('{//todo\nreturn 0;\n}\n')
        else:
            self.oOutputFileH.write(f'HANDLE m_hDll=NULL;\n')

            self.oOutputFileH.write(f'int LoadDll(char *pszFile);//加载dll文件，为m_hDll赋值。\n')
            self.oOutputFileCPP.write(f'int {self.sClassName}::LoadDll(char *pszFile)\n')
            self.oOutputFileCPP.write('{//todo\nreturn -1;\n}\n')

            self.oOutputFileH.write(f'void* GetFunc(char *pszName);//根据函数名获取地址\n')
            self.oOutputFileCPP.write(f'void* {self.sClassName}::GetFunc(char *pszName)\n')
            self.oOutputFileCPP.write('{//todo\nreturn NULL;\n}\n')

        return 0

    def WriteCheckFunc(self) -> int:

        self.oOutputFileH.write(f'int CheckAPI();//check func pointer. return 0 if no NULL, -1 if NULL exist\n')

        self.oOutputFileCPP.write(f'int {self.sClassName}::CheckAPI()\n{{\n')

        for it in self.lFunc:
            oFunc: CDllFunc = it
            self.oOutputFileCPP.write(f'if({oFunc.sMemberName}==NULL) return -1;\n')

        self.oOutputFileCPP.write('return 0;\n}\n')
        return 0

    def WriteFuncDec(self) -> int:
        for it in self.lFunc:
            oFunc: CDllFunc = it
            self.oOutputFileH.write(f'{oFunc.sRetType} {oFunc.sMethName}{oFunc.sArgs};\n')
        return 0

    def WriteMembers(self) -> int:
        for it in self.lFunc:
            oFunc: CDllFunc = it
            self.oOutputFileH.write(f'{oFunc.sFuncType} {oFunc.sMemberName}=NULL;\n')

            self.oOutputFileCPP.write(f'{oFunc.sRetType} {self.sClassName}::{oFunc.sMethName}{oFunc.sArgs}\n')
            self.oOutputFileCPP.write('{\n')
            sNewLine = f'return {oFunc.sMemberName}('

            for it in oFunc.lArgs:
                sNewLine += f'{it[1]}, '

            if len(oFunc.lArgs) > 0:
                sNewLine = sNewLine[0:len(sNewLine) - 2]
            sNewLine += ');\n'
            self.oOutputFileCPP.write(sNewLine)
            self.oOutputFileCPP.write('}\n')

        return 0

    def FuncDecToObject(self, oFunc: CFuncDec, sPrefix: str = '') -> CDllFunc:
        return CDllFunc(oFunc, sPrefix)

    def Make(self, sOutputPath: str, sClassName: str) -> int:
        iRet = 0
        iRead = 0
        # 打开文件#[
        oFile = Path(sOutputPath) / (sClassName + '.h')
        r = self.CreateFile(oFile)
        if isinstance(r, BaseException):
            return Exception(f'cannot open file {oFile}')
        self.oOutputFileH = r

        oFile = Path(sOutputPath) / (sClassName + '.cpp')
        r = self.CreateFile(oFile)
        if isinstance(r, BaseException):
            return Exception(f'cannot open file {oFile}')
        self.oOutputFileCPP = r
        # ]
        # 读取#[
        self.lFunc = []
        while iRead < len(self.sHead):
            r = self.FindNextFunc(iRead)
            if isinstance(r, CFuncDec):
                self.lFunc.append(self.FuncDecToObject(r, sClassName))
                iRead = r.tPos[1] + 1
                print(r.sName)
            else:
                break

        iRet = len(self.lFunc)
        # ]
        # 写入#[
        self.sClassName = sClassName

        self.oOutputFileH.write('#pragma once\n')
        self.oOutputFileCPP.write('//#include \"\"\n')

        self.WriteFuncType()

        self.oOutputFileH.write('class ' + sClassName + '\n')
        self.oOutputFileH.write('{\npublic:\n')
        self.WriteLoadFunc()
        self.WriteCheckFunc()

        self.WriteFuncDec()
        self.oOutputFileH.write('protected:\n')
        self.WriteLoadFunc('protected')
        self.WriteMembers()

        self.oOutputFileH.write('};\n')
        # ]

        return iRet

    def FindNextFunc(self, iStart: int) -> CFuncDec:
        oRet = CFuncDec()

        iTagStart = self.sHead.find(self.sAPITag, iStart)
        if iTagStart < 0:
            return None
        iTagEnd = iTagStart + len(self.sAPITag)

        r = self.sHead.rfind('\n', iStart, iTagStart)
        if r < 0:
            return None
        iDecStart = r + 1
        iDecEnd = self.sHead.find(';', iDecStart)
        if iDecEnd < 0:
            return None
        oRet.tPos = (iDecStart, iDecEnd)

        sDec = self.sHead[iDecStart:iTagStart].strip() + ' ' + self.sHead[iTagEnd:iDecEnd].strip()

        r1 = sDec.find('(')
        r2 = sDec.find(')')
        if r1 < 0 or r2 < 0:
            return None
        oRet.sArgs = sDec[r1:r2 + 1]

        def IsValid(s: str) -> bool:
            return s.isalpha() or s.isdigit() or s == '_'

        iName = r1 - 1
        while iName >= 0:
            if not IsValid(sDec[iName]):
                break
            iName-=1
            #while
        iName += 1
        oRet.sName = sDec[iName:r1].strip()
        oRet.sRetType = sDec[:iName].strip()

        return oRet
